fix(processor): dead-letter pubsub messages whose blob is persistently missing#3041
Open
dgellman wants to merge 2 commits into
Open
fix(processor): dead-letter pubsub messages whose blob is persistently missing#3041dgellman wants to merge 2 commits into
dgellman wants to merge 2 commits into
Conversation
…y missing The ingestor previously retried blob NotFound responses indefinitely: process.Subscribe returned nil without acking, so NATS redelivered the same orphaned message forever, generating hundreds of thousands of GetObject errors per processor over weeks. After maxBlobNotFoundRetries (3) consecutive NotFound responses for a message, ack the pubsub message to stop redelivery. Prefer the broker's native delivery count (NATS JetStream NumDelivered) so the threshold is shared across replicas and survives processor restarts; fall back to an in-process per-key counter for backends that do not expose one. Adds a Prometheus counter guac_processor_blob_notfound_dropped_total (label processor_id) and structured warn logs so operators can alert on orphan accumulation. Other Read errors retain previous redelivery behavior. Signed-off-by: Daniel Gellman <dgellman8@gmail.com>
31d3007 to
248c530
Compare
mihaimaruseac
approved these changes
May 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The processor previously retried blob
NotFoundresponses indefinitely.process.Subscribereturnednilfrom its handler without acking, so NATS redelivered the same orphaned message forever — generating hundreds of thousands ofGetObjecterrors per processor over weeks for blobs that no longer exist.After
maxBlobNotFoundRetries(3) consecutiveNotFoundresponses for a message, the message is now acked to stop redelivery. Other read errors retain their previous redelivery behavior.How retries are counted
When the underlying transport exposes a delivery count, that is preferred so the threshold is shared across replicas and survives processor restarts:
Metadata().NumDeliveredviagocloud.dev/pubsub.Message.As(&jetstream.Msg).mempubsubin tests): falls back to a per-key in-process counter.A Prometheus counter
guac_processor_blob_notfound_dropped_total(labelprocessor_id) is registered for alerting on orphan accumulation, and a structured warn log is emitted on each drop with the blob key and attempt count.What this does not address
Why orphan events get published in the first place (likely TTL mismatch between blob lifecycle and NATS retention, or out-of-band cleanup). That belongs in a separate investigation.
Test plan
Run on this branch:
go build ./...clean (exit 0)go vet ./...clean (exit 0)go test -race -timeout=60s ./... -count=1— 79 packages OK, 0 fail, 0 data races. Note: at the Makefile's default-timeout=30sunder race-detector parallelism, the unrelatedpkg/ingestor/parser/common/scannerpackage occasionally tips over (it runs ~25–30 s in isolation, with the same behaviour onmain); raising the timeout to 60 s clears it.golangci-lint run ./pkg/handler/processor/process/...— 0 issuesTestNotFoundTracker(per-key increment/clear isolation) andTestDeliveryCountSource(label values). ExistingTest_ProcessSubscribecontinues to pass unchanged.